package jmine.tec.report.impl.table.pos;

import static java.lang.Boolean.FALSE;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.List;

import jmine.tec.report.api.ReportElement;
import jmine.tec.report.api.table.ReportCell;
import jmine.tec.report.api.table.ReportColumn;
import jmine.tec.report.api.table.ReportRow;
import jmine.tec.report.api.table.ReportTable;
import jmine.tec.report.pos.ReportElementPosRenderer;

/**
 * Renderizador de tabelas par arquivos posicionais.
 * 
 * @author katsuo.ogawa
 */
public class ReportTablePosRenderer implements ReportElementPosRenderer {

    /**
     * Parâmetro que define o caractere para quebrar linha. <br/>
     * O valor é uma string. Caso não seje informado, é utilizado '/n'.
     */
    public static final String PARAM_POS_LINE_SEPARATOR = "positionalLineSeparator";

    /**
     * Parâmetro que define o tamanho do campo no arquivo posicional em bytes. <br/>
     * O valor é uma string que pode ser convertido em Long.
     */
    public static final String PARAM_BYTE_SIZE = "byteSize";

    /**
     * Parâmetro que define o encoding do arquivo. <br/>
     * O valor é uma string.
     */
    public static final String PARAM_POS_CHARSET = "positionalCharset";

    /**
     * Parâmetro que define como será o formato da data a ser parametrizada. <br/>
     * O valor é uma string. Caso não seje informado, é utilizado dd/MM/yyyy.
     */
    public static final String PARAM_DATE_PATTERN = "datePattern";

    /**
     * Parâmetro que define como será o formato do timestamp a ser parametrizado. <br/>
     * O valor é uma string. Caso não seja informado, é utilizado dd/MM/yyyy HH:mm:ss.
     */
    public static final String PARAM_TIMESTAMP_PATTERN = "timestampPattern";

    /**
     * Parâmetro que afirma que o cabeçalho da coluna não deve ser renderizada. O valor deve ficar disponível apenas programaticamente. <br/>
     * O valor é uma string que pode ser convertida para boolean.
     */
    public static final String PARAM_HEADER_HIDDEN = "isHeaderHidden";

    /**
     * Parâmetro que afirma que o rodapé da coluna não deve ser renderizada. O valor deve ficar disponível apenas programaticamente. <br/>
     * O valor é uma string que pode ser convertida para boolean.
     */
    public static final String PARAM_FOOTER_HIDDEN = "isFooterHidden";

    private CellRendererPosRegistry cellRendererRegistry;

    /**
     * {@inheritDoc}
     */
    public String getRendererType() {
        return ReportTable.RENDERER;
    }

    /**
     * {@inheritDoc}
     */
    public void render(OutputStream outputStream, ReportElement element) throws IOException {
        ReportTable<?> tableElement = (ReportTable<?>) element;
        String lineSeparator = element.getParameters().getString(PARAM_POS_LINE_SEPARATOR, String.valueOf('\n'));

        if (!tableElement.getParameters().getBoolean(PARAM_HEADER_HIDDEN, FALSE)) {
            this.generateFileHeader(outputStream, tableElement, lineSeparator);
        }

        this.generateFileDetail(outputStream, tableElement, lineSeparator);

        if (!tableElement.getParameters().getBoolean(PARAM_FOOTER_HIDDEN, FALSE)) {
            this.generateFileFooter(outputStream, tableElement, lineSeparator);
        }
    }

    /**
     * Gera o cabeçalho do arquivo.
     * 
     * @param <V> tipo de elemento das colunas
     * @param outputStream {@link OutputStream}
     * @param element Tabela que contém o cabeçalho a ser criado.
     * @param lineSeparator Caractere que representa a quebra de linha.
     * @throws IOException IOException
     */
    private <V> void generateFileHeader(OutputStream outputStream, ReportTable<V> element, String lineSeparator) throws IOException {
        boolean hasHeader = false;
        List<ReportColumn<V>> columns = element.getColumns();
        for (int col = 0; col < columns.size(); col++) {
            ReportCell cell = columns.get(col).getHeader();
            if (cell != null) {
                this.renderCell(outputStream, cell);
                hasHeader = true;
            }
        }
        if (hasHeader) {
            outputStream.write(lineSeparator.getBytes(element.getParameters().getString(PARAM_POS_CHARSET)));
        }
    }

    /**
     * Gera os detalhes do arquivo.
     * 
     * @param <V> record type
     * @param outputStream {@link OutputStream}
     * @param element {@link ReportTable}
     * @param lineSeparator Caractere que representa a quebra de linha.
     * @throws IOException IOException
     */
    private <V> void generateFileDetail(OutputStream outputStream, ReportTable<V> element, String lineSeparator) throws IOException {
        Iterator<ReportRow<V>> rowIterator = element.createRowIterator();
        while (rowIterator.hasNext()) {
            ReportRow<V> row = rowIterator.next();
            for (ReportColumn column : element.getColumns()) {
                ReportCell cell = row.getCellMap().get(column.getId());
                this.renderCell(outputStream, cell);
            }
            outputStream.write(lineSeparator.getBytes(element.getParameters().getString(PARAM_POS_CHARSET)));
        }
    }

    /**
     * Gera o rodapé do arquivo.
     * 
     * @param <V> tipo de elemento das colunas
     * @param outputStream {@link OutputStream}
     * @param element {@link ReportTable}
     * @param lineSeparator Caractere de quebra de linha.
     * @throws IOException IOException
     */
    private <V> void generateFileFooter(OutputStream outputStream, ReportTable<V> element, String lineSeparator) throws IOException {
        boolean hasFooter = false;
        List<ReportColumn<V>> columns = element.getColumns();
        for (int col = 0; col < columns.size(); col++) {
            ReportCell cell = columns.get(col).getFooter();
            if (cell != null) {
                this.renderCell(outputStream, cell);
                hasFooter = true;
            }
        }
        if (hasFooter) {
            outputStream.write(lineSeparator.getBytes(element.getParameters().getString(PARAM_POS_CHARSET)));
        }
    }

    /**
     * Renderiza a celula no OutputStream.
     * 
     * @param outputStream {@link OutputStream}
     * @param cell {@link ReportCell}
     * @throws IOException IOException
     */
    private void renderCell(OutputStream outputStream, ReportCell cell) throws IOException {
        ReportCellPosRenderer renderer = this.cellRendererRegistry.getRenderer(cell.getRendererType());
        renderer.render(outputStream, cell);
    }

    /**
     * @return {@link CellRendererPosRegistry}
     */
    public CellRendererPosRegistry getCellRendererRegistry() {
        return this.cellRendererRegistry;
    }

    /**
     * @param cellRendererRegistry {@link CellRendererPosRegistry}
     */
    public void setCellRendererRegistry(CellRendererPosRegistry cellRendererRegistry) {
        this.cellRendererRegistry = cellRendererRegistry;
    }

}
